package org.bouncycastle.pqc.crypto.qtesla;

import java.security.SecureRandom;

import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.pqc.crypto.MessageSigner;

/**
 * Signer for the qTESLA algorithm (https://qtesla.org/)
 */
public class QTESLASigner
    implements MessageSigner
{
    /**
     * The Public Key of the Identity Whose Signature Will be Generated
     */
    private QTESLAPublicKeyParameters publicKey;

    /**
     * The Private Key of the Identity Whose Signature Will be Generated
     */
    private QTESLAPrivateKeyParameters privateKey;

    /**
     * The Source of Randomness for private key operations
     */
    private SecureRandom secureRandom;

    public QTESLASigner()
    {
    }

    /**
     * Initialise the signer.
     *
     * @param forSigning true if we are generating a signature, false
     *                   otherwise.
     * @param param      ParametersWithRandom containing a private key for signature generation, public key otherwise.
     */
    public void init(boolean forSigning, CipherParameters param)
    {
         if (forSigning)
         {
             if (param instanceof ParametersWithRandom)
             {
                 this.secureRandom = ((ParametersWithRandom)param).getRandom();
                 privateKey = (QTESLAPrivateKeyParameters)((ParametersWithRandom)param).getParameters();
             }
             else
             {
                 this.secureRandom = CryptoServicesRegistrar.getSecureRandom();
                 privateKey = (QTESLAPrivateKeyParameters)param;
             }
             publicKey = null;
             QTESLASecurityCategory.validate(privateKey.getSecurityCategory());
         }
         else
         {
             privateKey = null;
             publicKey = (QTESLAPublicKeyParameters)param;
             QTESLASecurityCategory.validate(publicKey.getSecurityCategory());
         }
    }

    /**
     * Generate a signature directly for the passed in message.
     *
     * @param message the message to be signed.
     * @return the signature generated.
     */
    public byte[] generateSignature(byte[] message)
    {
        byte[] sig = new byte[QTESLASecurityCategory.getSignatureSize(privateKey.getSecurityCategory())];

        switch (privateKey.getSecurityCategory())
        {
        case QTESLASecurityCategory.HEURISTIC_I:
            QTESLA.signingI(sig, message, 0, message.length, privateKey.getSecret(), secureRandom);
            break;
        case QTESLASecurityCategory.HEURISTIC_III_SIZE:
            QTESLA.signingIIISize(sig, message, 0, message.length, privateKey.getSecret(), secureRandom);
            break;
        case QTESLASecurityCategory.HEURISTIC_III_SPEED:
            QTESLA.signingIIISpeed(sig, message, 0, message.length, privateKey.getSecret(), secureRandom);
            break;
        case QTESLASecurityCategory.PROVABLY_SECURE_I:
            QTESLA.signingIP(sig, message, 0, message.length, privateKey.getSecret(), secureRandom);
            break;
        case QTESLASecurityCategory.PROVABLY_SECURE_III:
            QTESLA.signingIIIP(sig, message, 0, message.length, privateKey.getSecret(), secureRandom);
            break;
        default:
            throw new IllegalArgumentException("unknown security category: " + privateKey.getSecurityCategory());
        }

        return sig;
    }

    /**
     * Verify the signature against the passed in message.
     *
     * @param message the message that was supposed to have been signed.
     * @param signature the signature of the message
     * @return true if the signature passes, false otherwise.
     */
    public boolean verifySignature(byte[] message, byte[] signature)
    {
        int status;

        switch (publicKey.getSecurityCategory())
        {
        case QTESLASecurityCategory.HEURISTIC_I:
            status = QTESLA.verifyingI(message, signature, 0, signature.length, publicKey.getPublicData());
            break;
        case QTESLASecurityCategory.HEURISTIC_III_SIZE:
            status = QTESLA.verifyingIIISize(message, signature, 0, signature.length, publicKey.getPublicData());
            break;
        case QTESLASecurityCategory.HEURISTIC_III_SPEED:
            status = QTESLA.verifyingIIISpeed(message, signature, 0, signature.length, publicKey.getPublicData());
            break;
        case QTESLASecurityCategory.PROVABLY_SECURE_I:
            status = QTESLA.verifyingPI(message, signature, 0, signature.length, publicKey.getPublicData());
            break;
        case QTESLASecurityCategory.PROVABLY_SECURE_III:
            status = QTESLA.verifyingPIII(message, signature, 0, signature.length, publicKey.getPublicData());
            break;
        default:
            throw new IllegalArgumentException("unknown security category: " + publicKey.getSecurityCategory());
        }

        return 0 == status;
    }
}
